# <include-final_project/utils.py>
# <imports>
import numpy as np
import pandas as pd
import plotly.io as pio
from final_project import utils
pd.options.plotting.backend = "plotly"
pio.templates.default = "seaborn"
pio.renderers.default = "notebook_connected+vscode"
This is the exploration of a spread trading stategy involving BTCUSDT and BTCUSDT perpetual futures contracts on the binance exchange.
The basic strategy is to receive the funding rate when it is attractive and taking advantage of the fact the perptual contract and underlying asset prices converge over time as as result of the funding rate. We do this by tracking the funding rate and shorting the perpetual contract and going long the underlying asset when the funding rate is high and closing positions when the funding rate comes back down. If the funding rate goes sufficiently negative, we go long the perpetual contract and short the underlying asset.
The perptual futures contract has a funding rate that are periodic payments made to either short or long traders based on the difference in the perpetual futures price and the spot price. When the market is bullish - perpetual futures price greater than the spot price - the funding rate is positive and long traders pay short traders. When it is bearish, the funding rate is negative and short traders pay long traders.
Funding rate payments are made every 8 hours starting at 00:00 UTC and only gets paid if positions are held at the designated time.
The actual rate has two components, an interest rate and a premium. The interest rate is set by the exchange and may change based on market conditions, such as changes in the federal funds rate. The current interest rate is 0.01% per eight hours, which equates to 0.03% per day or 10.8% per year. The premium is determined based on the bid ask spread relative to an index formed from a bucket of prices from major spot market exchanges need to understand better. At this point, to begin evaluating the strategy, we use the historical funding rates as provided by the exhange, and have on the todo list a fuller understanding of the mechanics of determining the funding rate.
Both assets are subject to automatic liquidation when collateral = initial collateral + realized and unrealized profits and losses is less than the maintenance margin. Maintenance margin is determined based on position size an leverage. Perpetual futures contracts can be traded with leverage up to 125x. need to understand better
df_exch = utils.get_exchange_info()
df_exch.loc["BTCUSDT"]
status TRADING
baseAsset BTC
baseAssetPrecision 8
quoteAsset USDT
quotePrecision 8
quoteAssetPrecision 8
baseCommissionPrecision 8
quoteCommissionPrecision 8
orderTypes [LIMIT, LIMIT_MAKER, MARKET, STOP_LOSS_LIMIT, ...
icebergAllowed True
ocoAllowed True
quoteOrderQtyMarketAllowed True
isSpotTradingAllowed True
isMarginTradingAllowed True
filters [{'filterType': 'PRICE_FILTER', 'minPrice': '0...
permissions [SPOT, MARGIN]
Name: BTCUSDT, dtype: object
tick_params = dict(
interval="8h",
start_time="2018-09-08"
)
df_perpetual = utils.get_continuous_contracts(pair="BTCUSDT", **tick_params)
fig = utils.make_price_volume_chart(
df_perpetual,
title="BTCUSDT Perpetual Contracts"
)
fig.show()
fig = utils.make_overview_chart(df_perpetual.per_return, title="BTCUSDT Perpetual", subtitle_base="Log Returns")
fig.show()
These are the same prices as above.
df_spot = utils.get_klines(symbol="BTCUSDT", **tick_params)
fig = utils.make_price_volume_chart(df_spot, title="BTCUSDT Spot Price OHLC")
fig.show()
fig = utils.make_overview_chart(
df_spot.per_return, title="BTCUSDT Spot",
subtitle_base="Log Returns"
)
fig.show()
It looks like the funding rate rarely goes negative. Is that because the perpetual price rarely goes below the spot price or is there something structural that may present and arbitrage opportunity going on?
df_funding = utils.get_funding_rate_history(symbol="BTCUSDT", start_time=tick_params["start_time"])
fig = df_funding.fundingRate.plot(title="BTCUSDT Funding Rate")
fig.update_traces(line=dict(width=1))
fig.update(layout_showlegend=False)
fig.show()
The cumulative funding rate essentially shows what return would have been generated by opening one trade short the perpetual and long the spot and holding it for the entire period.
fig = utils.make_overview_chart(
df_funding.fundingRate, title="Funding Rate",
subtitle_base="Funding Rate"
)
fig.show()
This is of the spread itself - the percentage difference between the perpetual and spot prices and excludes a spread outlier on 2020-12-21 of 0.018.
spread = (df_perpetual.close / df_spot.close - 1)
spread.name = "spread"
fig = utils.make_2_yaxis_lines(
spread[spread.abs() < .015], df_funding.fundingRate,
title="Perpetual - Spot Spread vs. Funding Rate"
)
fig.show()
# print(f"correlation: {spread[spread.abs() < .015].corr(df_funding.fundingRate):0.4f}")
fig = utils.make_overview_chart(
spread[spread.abs() < .015],
title="Perpetual Spot Spread",
subtitle_base="Spread"
)
fig.show()
This shows that the difference in returns between the perpetual and the spot basically revert to zero over time, which means that there is profit to be made in chasing the funding rate, shorting the perpetual while going long the underlying asset to get the funding rate.
spread = (df_perpetual.per_return - df_spot.per_return).rolling(9).sum()
spread.name = "spread"
fig = utils.make_2_yaxis_lines(
spread[spread.abs() < .015], df_funding.fundingRate.rolling(9).sum(),
title="Perpetual - Spot Return Spread vs. Funding Rate"
)
# print(f"correlation: {spread[spread.abs() < .015].corr(df_funding.fundingRate):0.4f}")
fig.show()
This strategy is essentially designed to capture the funding rate, shorting the perpertual and going long the underlying asset when the funding rate is high.
A better strategy would be to simply hold a short position in the perpetual and a long position in the spot and close it out much less frequently in order to avoid transaction costs.
df_ticks = utils.get_ticks(df_perpetual, df_spot, df_funding, dollar_position_size=100000)
df_ticks.head()
| series | adj_close | adj_open | adj_return | position_size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| asset | perpetual | spot | perpetual | spot | funding_rate | funding_rate_prior | perpetual | spot | spread | perpetual | spot |
| date | |||||||||||
| 2019-09-10 16:00:00 | 10102.02 | 10098.15 | 10172.13 | 10176.26 | 0.0001 | 0.0001 | -0.007601 | -0.007588 | 0.000383 | 9.899010 | 9.902804 |
| 2019-09-11 00:00:00 | 10067.46 | 10066.00 | 10094.27 | 10098.19 | 0.0001 | 0.0001 | -0.003427 | -0.003189 | 0.000145 | 9.932992 | 9.934433 |
| 2019-09-11 08:00:00 | 9991.84 | 9990.07 | 10068.41 | 10066.38 | 0.0001 | 0.0001 | -0.007540 | -0.007572 | 0.000177 | 10.008167 | 10.009940 |
| 2019-09-11 16:00:00 | 10159.55 | 10158.33 | 9992.18 | 9990.57 | 0.0001 | 0.0001 | 0.016645 | 0.016702 | 0.000120 | 9.842956 | 9.844138 |
| 2019-09-12 00:00:00 | 10078.96 | 10079.79 | 10163.06 | 10158.75 | 0.0001 | 0.0001 | -0.007964 | -0.007762 | -0.000082 | 9.921659 | 9.920842 |
df_ticks.adj_close.perpetual.shift().dropna().to_frame()
| perpetual | |
|---|---|
| date | |
| 2019-09-11 00:00:00 | 10102.02 |
| 2019-09-11 08:00:00 | 10067.46 |
| 2019-09-11 16:00:00 | 9991.84 |
| 2019-09-12 00:00:00 | 10159.55 |
| 2019-09-12 08:00:00 | 10078.96 |
| ... | ... |
| 2021-05-31 16:00:00 | 36944.12 |
| 2021-06-01 00:00:00 | 37243.38 |
| 2021-06-01 08:00:00 | 36817.43 |
| 2021-06-01 16:00:00 | 36233.59 |
| 2021-06-02 00:00:00 | 36693.41 |
1891 rows × 1 columns
df_bench = pd.concat([
df_ticks.adj_close.perpetual.iloc[1:] * df_ticks.adj_return.funding_rate_prior.iloc[1:],
- df_ticks.adj_close.perpetual.diff().dropna(),
df_ticks.adj_close.spot.diff().dropna()
], axis=1)
df_bench.columns = ["funding", "perpetual", "spot"]
df_bench["spread"] = df_bench.perpetual + df_bench.spot
df_bench["total"] = df_bench.funding + df_bench.spread
df_bench = df_bench.div(df_ticks.adj_close.perpetual.shift().dropna(), axis=0)
df_bench
| funding | perpetual | spot | spread | total | |
|---|---|---|---|---|---|
| date | |||||
| 2019-11-28 00:00:00 | 0.000100 | 0.003541 | -0.002688 | 0.000852 | 0.000952 |
| 2019-11-28 08:00:00 | 0.000101 | -0.011844 | 0.011712 | -0.000132 | -0.000030 |
| 2019-11-28 16:00:00 | 0.000014 | 0.021459 | -0.021264 | 0.000195 | 0.000209 |
| 2019-11-29 00:00:00 | -0.000108 | -0.013490 | 0.013291 | -0.000199 | -0.000307 |
| 2019-11-29 08:00:00 | -0.000234 | -0.027146 | 0.027146 | 0.000000 | -0.000234 |
| ... | ... | ... | ... | ... | ... |
| 2021-05-31 16:00:00 | 0.000103 | -0.025926 | 0.026032 | 0.000106 | 0.000209 |
| 2021-06-01 00:00:00 | 0.000098 | 0.019942 | -0.019813 | 0.000129 | 0.000227 |
| 2021-06-01 08:00:00 | 0.000097 | 0.031968 | -0.031685 | 0.000283 | 0.000380 |
| 2021-06-01 16:00:00 | 0.000103 | -0.026118 | 0.025736 | -0.000382 | -0.000279 |
| 2021-06-02 00:00:00 | 0.000098 | 0.024235 | -0.024394 | -0.000159 | -0.000062 |
1657 rows × 5 columns
fig = utils.make_overview_chart(
df_bench.total,
title="Passive Strategy",
subtitle_base="8h Return"
)
fig.show()
Return would have been 53.1% on a compounded basis.
(df_bench.total + 1).prod()
1.7519258010959269
fig = utils.make_overview_chart(
df_bench.resample("M").sum().total,
title="Passive Strategy",
subtitle_base="Monthly Return"
)
fig.show()
strategy_params = dict(
pair=("spot", "perpetual"),
df_ticks=df_ticks,
spread_column=("adj_return", "funding_rate"),
open_threshold=0.0005,
close_threshold=0.0002,
run=True,
transact_cost_percent = 0.0005,
closed_positions = [],
)
strategy = utils.Strategy(**strategy_params)
fig = strategy.plot()
fig.show()
df_ticks.loc["2020-05-08":].head(10)
| series | adj_close | adj_open | adj_return | position_size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| asset | perpetual | spot | perpetual | spot | funding_rate | funding_rate_prior | perpetual | spot | spread | perpetual | spot |
| date | |||||||||||
| 2020-05-08 00:00:00 | 9818.52 | 9810.14 | 9994.74 | 9986.30 | 0.000161 | 0.000100 | -0.017815 | -0.017808 | 0.000854 | 10.184834 | 10.193534 |
| 2020-05-08 08:00:00 | 9949.28 | 9941.21 | 9818.61 | 9810.34 | 0.000340 | 0.000161 | 0.013230 | 0.013272 | 0.000812 | 10.050979 | 10.059138 |
| 2020-05-08 16:00:00 | 9807.49 | 9800.01 | 9949.61 | 9942.07 | 0.000532 | 0.000340 | -0.014354 | -0.014305 | 0.000763 | 10.196289 | 10.204071 |
| 2020-05-09 00:00:00 | 9609.65 | 9592.77 | 9809.63 | 9800.02 | 0.000420 | 0.000532 | -0.020379 | -0.021374 | 0.001760 | 10.406206 | 10.424518 |
| 2020-05-09 08:00:00 | 9698.04 | 9688.62 | 9608.49 | 9594.70 | 0.000758 | 0.000420 | 0.009156 | 0.009942 | 0.000972 | 10.311362 | 10.321387 |
| 2020-05-09 16:00:00 | 9550.67 | 9539.40 | 9697.30 | 9688.55 | 0.000580 | 0.000758 | -0.015312 | -0.015521 | 0.001181 | 10.470470 | 10.482840 |
| 2020-05-10 00:00:00 | 8807.73 | 8812.28 | 9550.25 | 9539.10 | 0.000423 | 0.000580 | -0.080982 | -0.079284 | -0.000516 | 11.353663 | 11.347801 |
| 2020-05-10 08:00:00 | 8649.62 | 8650.96 | 8805.67 | 8812.27 | 0.000100 | 0.000423 | -0.018114 | -0.018476 | -0.000155 | 11.561202 | 11.559411 |
| 2020-05-10 16:00:00 | 8719.53 | 8722.77 | 8649.73 | 8652.27 | 0.000100 | 0.000100 | 0.008050 | 0.008267 | -0.000371 | 11.468508 | 11.464248 |
| 2020-05-11 00:00:00 | 8660.96 | 8664.32 | 8723.23 | 8722.77 | 0.000100 | 0.000100 | -0.006740 | -0.006723 | -0.000388 | 11.546064 | 11.541587 |
Here we trade on the spread, increasing the open threshold, but it doesn't actually do much better than the funding rate only strategy, since the bulk of the profit comes from one trade on an outlier.
strategy_params = dict(
pair=("spot", "perpetual"),
df_ticks=df_ticks,
spread_column=("adj_return", "spread"),
open_threshold=0.0015,
close_threshold=0.0002,
run=True,
transact_cost_percent = 0.0005,
closed_positions = [],
)
strategy = utils.Strategy(**strategy_params)
fig = strategy.plot()
fig.show()
tick_params = dict(
interval="1m",
start_time="2021-04-30",
end_time="2021-05-10",
)
df_perpetual = utils.get_continuous_contracts(pair="BTCUSDT", **tick_params)
df_spot = utils.get_klines(symbol="BTCUSDT", **tick_params)
df_ticks = utils.get_ticks(df_perpetual, df_spot, df_funding, dollar_position_size=100000)
df_ticks
| series | adj_close | adj_open | adj_return | position_size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| asset | perpetual | spot | perpetual | spot | funding_rate | funding_rate_prior | perpetual | spot | spread | perpetual | spot |
| date | |||||||||||
| 2021-04-30 00:01:00 | 53497.98 | 53498.26 | 53529.06 | 53529.20 | 0.000100 | 0.000100 | -0.000583 | -0.000578 | -0.000005 | 1.869229 | 1.869220 |
| 2021-04-30 00:02:00 | 53486.65 | 53488.16 | 53497.98 | 53498.25 | 0.000100 | 0.000100 | -0.000212 | -0.000189 | -0.000028 | 1.869625 | 1.869573 |
| 2021-04-30 00:03:00 | 53588.50 | 53583.02 | 53487.48 | 53488.16 | 0.000100 | 0.000100 | 0.001902 | 0.001772 | 0.000102 | 1.866072 | 1.866263 |
| 2021-04-30 00:04:00 | 53599.61 | 53585.83 | 53591.22 | 53583.03 | 0.000100 | 0.000100 | 0.000207 | 0.000052 | 0.000257 | 1.865685 | 1.866165 |
| 2021-04-30 00:05:00 | 53524.60 | 53509.93 | 53599.62 | 53585.83 | 0.000100 | 0.000100 | -0.001400 | -0.001417 | 0.000274 | 1.868300 | 1.868812 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2021-05-09 23:56:00 | 58234.32 | 58180.19 | 58200.79 | 58143.07 | 0.000639 | 0.000639 | 0.000576 | 0.000638 | 0.000930 | 1.717200 | 1.718798 |
| 2021-05-09 23:57:00 | 58236.95 | 58187.90 | 58234.32 | 58180.19 | 0.000639 | 0.000639 | 0.000045 | 0.000133 | 0.000843 | 1.717123 | 1.718570 |
| 2021-05-09 23:58:00 | 58279.88 | 58225.32 | 58236.96 | 58187.90 | 0.000639 | 0.000639 | 0.000737 | 0.000643 | 0.000937 | 1.715858 | 1.717466 |
| 2021-05-09 23:59:00 | 58292.53 | 58240.84 | 58279.88 | 58225.32 | 0.000639 | 0.000639 | 0.000217 | 0.000267 | 0.000888 | 1.715486 | 1.717008 |
| 2021-05-10 00:00:00 | 58374.94 | 58327.60 | 58292.53 | 58240.83 | 0.000335 | 0.000639 | 0.001413 | 0.001489 | 0.000812 | 1.713064 | 1.714454 |
14400 rows × 11 columns
strategy_params = dict(
pair=("spot", "perpetual"),
df_ticks=df_ticks,
spread_column=("adj_return", "spread"),
open_threshold=0.0015,
close_threshold=0.0002,
run=True,
transact_cost_percent = 0.0005,
closed_positions = [],
)
strategy = utils.Strategy(**strategy_params)
fig = strategy.plot()
fig.show()
tick_params = dict(
interval="8h",
start_time="2020-05-01",
)
df_perpetual = utils.get_continuous_contracts(pair="ETHUSDT", **tick_params)
df_spot = utils.get_klines(symbol="ETHUSDT", **tick_params)
df_funding = utils.get_funding_rate_history(symbol="ETHUSDT", start_time=tick_params["start_time"])
fig = utils.make_overview_chart(
df_funding.fundingRate, title="Funding Rate",
subtitle_base="Funding Rate"
)
fig.show()
df_ticks = utils.get_ticks(df_perpetual, df_spot, df_funding, dollar_position_size=100000)
df_ticks
| series | adj_close | adj_open | adj_return | position_size | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| asset | perpetual | spot | perpetual | spot | funding_rate | funding_rate_prior | perpetual | spot | spread | perpetual | spot |
| date | |||||||||||
| 2020-05-01 08:00:00 | 210.06 | 209.80 | 212.85 | 212.54 | 0.000196 | 0.000100 | -0.013054 | -0.013070 | 0.001239 | 476.054461 | 476.644423 |
| 2020-05-01 16:00:00 | 212.22 | 212.02 | 210.05 | 209.80 | 0.000335 | 0.000196 | 0.010230 | 0.010526 | 0.000943 | 471.209123 | 471.653618 |
| 2020-05-02 00:00:00 | 211.27 | 211.12 | 212.21 | 212.01 | 0.000203 | 0.000335 | -0.004487 | -0.004254 | 0.000710 | 473.327969 | 473.664267 |
| 2020-05-02 08:00:00 | 213.23 | 212.96 | 211.27 | 211.09 | 0.000288 | 0.000203 | 0.009234 | 0.008678 | 0.001268 | 468.977161 | 469.571751 |
| 2020-05-02 16:00:00 | 213.99 | 213.90 | 213.22 | 213.02 | 0.000655 | 0.000288 | 0.003558 | 0.004404 | 0.000421 | 467.311557 | 467.508181 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 2021-05-31 16:00:00 | 2706.33 | 2706.15 | 2637.94 | 2637.47 | 0.000100 | 0.000100 | 0.025595 | 0.025703 | 0.000067 | 36.950409 | 36.952867 |
| 2021-06-01 00:00:00 | 2652.36 | 2652.53 | 2706.46 | 2706.15 | 0.000100 | 0.000100 | -0.020144 | -0.020013 | -0.000064 | 37.702273 | 37.699856 |
| 2021-06-01 08:00:00 | 2567.57 | 2568.49 | 2652.35 | 2652.37 | 0.000100 | 0.000100 | -0.032490 | -0.032196 | -0.000358 | 38.947332 | 38.933381 |
| 2021-06-01 16:00:00 | 2634.63 | 2634.57 | 2567.57 | 2568.62 | 0.000100 | 0.000100 | 0.025783 | 0.025402 | 0.000023 | 37.955994 | 37.956858 |
| 2021-06-02 00:00:00 | 2606.53 | 2605.92 | 2634.61 | 2634.31 | 0.000100 | 0.000100 | -0.010723 | -0.010934 | 0.000234 | 38.365183 | 38.374163 |
1191 rows × 11 columns
strategy_params = dict(
pair=("spot", "perpetual"),
df_ticks=df_ticks,
spread_column=("adj_return", "funding_rate"),
open_threshold=0.0005,
close_threshold=0.0001,
run=True,
transact_cost_percent = 0.0005,
closed_positions = [],
)
strategy = utils.Strategy(**strategy_params)
fig = strategy.plot()
fig.show()
pd.DataFrame(strategy.stats)
| date | funding_rate_profit | position_profit | transact_cost | realized_profit | unrealized_profit | total_profit | tick_profit | total_return | tick_return | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2020-05-01 08:00:00 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 0.0 | 0.000000 | 0.000000 |
| 1 | 2020-05-01 16:00:00 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 0.0 | 0.000000 | 0.000000 |
| 2 | 2020-05-02 00:00:00 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 0.0 | 0.000000 | 0.000000 |
| 3 | 2020-05-02 08:00:00 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.0 | 0.000000 | 0.0 | 0.000000 | 0.000000 |
| 4 | 2020-05-02 16:00:00 | 0.000000 | 0.000000 | -100.000000 | -100.000000 | -100.0 | -200.000000 | -200.0 | -0.001001 | -0.001001 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 1186 | 2021-05-31 16:00:00 | 155401.342774 | -61.769718 | -1361.766245 | 153977.806812 | 0.0 | 153977.806812 | 0.0 | 0.570917 | 0.000000 |
| 1187 | 2021-06-01 00:00:00 | 155401.342774 | -61.769718 | -1361.766245 | 153977.806812 | 0.0 | 153977.806812 | 0.0 | 0.570917 | 0.000000 |
| 1188 | 2021-06-01 08:00:00 | 155401.342774 | -61.769718 | -1361.766245 | 153977.806812 | 0.0 | 153977.806812 | 0.0 | 0.570917 | 0.000000 |
| 1189 | 2021-06-01 16:00:00 | 155401.342774 | -61.769718 | -1361.766245 | 153977.806812 | 0.0 | 153977.806812 | 0.0 | 0.570917 | 0.000000 |
| 1190 | 2021-06-02 00:00:00 | 155401.342774 | -61.769718 | -1361.766245 | 153977.806812 | 0.0 | 153977.806812 | 0.0 | 0.570917 | 0.000000 |
1191 rows × 10 columns
pd.DataFrame(strategy.closed_positions)
| position_type | open_date | security | shares | open_price | open_transact_cost | close_price | close_transact_cost | closed | close_date | transact_cost_percent | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | PositionType.LONG | 2020-05-02 16:00 | spot | 467.508181 | 213.90 | 50.0 | 201.23 | 47.038336 | True | 2020-05-15 00:00 | 0.0005 |
| 1 | PositionType.SHORT | 2020-05-02 16:00 | perpetual | 467.311557 | 213.99 | 50.0 | 201.09 | 46.985840 | True | 2020-05-15 00:00 | 0.0005 |
| 2 | PositionType.LONG | 2020-07-23 00:00 | spot | 381.010440 | 262.46 | 50.0 | 346.19 | 65.951002 | True | 2020-09-07 08:00 | 0.0005 |
| 3 | PositionType.SHORT | 2020-07-23 00:00 | perpetual | 380.633374 | 262.72 | 50.0 | 346.09 | 65.866702 | True | 2020-09-07 08:00 | 0.0005 |
| 4 | PositionType.LONG | 2020-10-21 16:00 | spot | 256.160664 | 390.38 | 50.0 | 2438.92 | 312.377683 | True | 2021-05-19 16:00 | 0.0005 |
| 5 | PositionType.SHORT | 2020-10-21 16:00 | perpetual | 255.780643 | 390.96 | 50.0 | 2442.98 | 312.433497 | True | 2021-05-19 16:00 | 0.0005 |
| 6 | PositionType.LONG | 2021-05-19 16:00 | perpetual | 40.933614 | 2442.98 | 50.0 | 2711.74 | 55.500659 | True | 2021-05-20 00:00 | 0.0005 |
| 7 | PositionType.SHORT | 2021-05-19 16:00 | spot | 41.001755 | 2438.92 | 50.0 | 2712.69 | 55.612525 | True | 2021-05-20 00:00 | 0.0005 |
wallet_balance = 1535443.01
df_positions = pd.DataFrame([
{"position": "ETHUSDT", "shares": 3683.979, "entry_price": 1456.84, "mark_price": 1335.18, "liquidation_price": 1153.25, "pnl": -447482.1, "maintenance_margin_rate": 0.10, "maintenance_amount": 135365},
{"position": "BTCUSDT", "shares": 109.488, "entry_price": 32481.98, "mark_price": 31967.27, "liquidation_price": 26316.86, "pnl": -56248.35, "maintenance_margin_rate": .025, "maintenance_amount": 16300}
]).set_index("position")
df_positions
| shares | entry_price | mark_price | liquidation_price | pnl | maintenance_margin_rate | maintenance_amount | |
|---|---|---|---|---|---|---|---|
| position | |||||||
| ETHUSDT | 3683.979 | 1456.84 | 1335.18 | 1153.25 | -447482.10 | 0.100 | 135365 |
| BTCUSDT | 109.488 | 32481.98 | 31967.27 | 26316.86 | -56248.35 | 0.025 | 16300 |
notional_value = df_positions.mark_price * df_positions.shares
notional_value
position ETHUSDT 4.918775e+06 BTCUSDT 3.500032e+06 dtype: float64
mainentance_margin = notional_value * df_positions.maintenance_margin_rate - df_positions.maintenance_amount
mainentance_margin
position ETHUSDT 356512.508122 BTCUSDT 71200.811444 dtype: float64
numer = wallet_balance - mainentance_margin.BTCUSDT + df_positions.pnl.BTCUSDT + df_positions.maintenance_amount.ETHUSDT - 1 * df_positions.shares.abs().ETHUSDT * df_positions.entry_price.ETHUSDT
denom = df_positions.shares.abs().ETHUSDT * df_positions.maintenance_margin_rate.ETHUSDT - 1 * df_positions.shares.abs().ETHUSDT
numer, denom, numer / denom
(-3823609.117803999, -3315.5811, 1153.2244280810985)
capital = 100000
leverage = 2
long_spot = (capital / (leverage + 1)) * leverage
short_perpetual = capital-long_spot
mainentance_margin_rate = 1 / leverage * 0.50
long_spot, short_perpetual, mainentance_margin_rate
(66666.66666666667, 33333.33333333333, 0.25)
75000 / (37500 * 1.00015)
1.999700044993251
short_perpetual
33333.33333333333
spot_price = 37500
spot_shares = long_spot / spot_price
spread = 1.001
perpetual_price = spot_price * spread
perpetual_shares = - short_perpetual / perpetual_price
df_positions = pd.DataFrame([
{"position": "perpetual", "shares": perpetual_shares, "entry_price": perpetual_price, "mark_price": 0., "liquidation_price": 0., "pnl": 0., "maintenance_margin_rate": mainentance_margin_rate, "maintenance_amount": mainentance_margin_rate * short_perpetual},
{"position": "spot", "shares": spot_shares, "entry_price": spot_price, "mark_price": 0., "liquidation_price": 0., "pnl": 0., "maintenance_margin_rate": 0., "maintenance_amount": 0.}
]).set_index("position")
df_positions
| shares | entry_price | mark_price | liquidation_price | pnl | maintenance_margin_rate | maintenance_amount | |
|---|---|---|---|---|---|---|---|
| position | |||||||
| perpetual | -0.888001 | 37537.5 | 0.0 | 0.0 | 0.0 | 0.25 | 8333.333333 |
| spot | 1.777778 | 37500.0 | 0.0 | 0.0 | 0.0 | 0.00 | 0.000000 |
df_positions.entry_price.perpetual - df_positions.maintenance_amount.perpetual / df_positions.shares.perpetual
46921.87499999999
df_positions.mark_price = 40000
df_positions.pnl = (df_positions.mark_price - df_positions.entry_price) * df_positions.shares
wallet_balance = (df_positions.shares * df_positions.mark_price).sum() - ((df_positions.shares * df_positions.entry_price).sum() - capital)
df_positions.maintenance_amount
position perpetual 8333.333333 spot 0.000000 Name: maintenance_amount, dtype: float64
numer = wallet_balance + df_positions.maintenance_amount.perpetual + 1 * df_positions.shares.abs().perpetual * df_positions.entry_price.perpetual
denom = df_positions.shares.abs().perpetual * df_positions.maintenance_margin_rate.perpetual - 1 * df_positions.shares.abs().perpetual
numer, denom, numer / denom
(143924.4089244089, -0.6660006660006661, -216102.49999999994)